#ifndef AST_Builder_GUARD
#define AST_Builder_GUARD

#include "../../SPA_Data_Types.h"
#include "AST_XLab2.h"
#include <stack>
#include <iostream>

using namespace std;

class AST_Builder {
private:
	//enum containerType { IF, WHILE };

	AST *_ast;
	class block{
	public:
		//containerType c_type;
		stmtNode* container;
		std::stack<stmtNode**> blockNext_tails;
		block( stmtNode *c ) {
			this->container = c;
		};
	};
	typedef struct AST_State {
		INTEGER numProcs;
		INTEGER numStmts;

		procNode *currProcNode;
		stmtNode *currStmtNode;
		std::stack<composite_expr*> exprStack;

		//stack<stmtNode*> container_stack;

		std::stack<stmtNode**> newNextLinks_stack;
		BOOLEAN setFollows;
		stmtNode** newFollowsLink;
		std::stack<block*> bts;

	} AST_State;
	AST_State _state;
	AST_Builder();
	static AST_Builder *obj;
public:
	static AST_Builder* getInstance();
	void addProcedure( PROC_INDEX proc_id ) {
		// add new procedure with
		// index = number of procedures in ast.

		// empty container stack
		while( !(_state.bts.empty()) )
			_state.bts.pop();
		// wipe all next links..
		while( !(_state.newNextLinks_stack.empty()) )
			_state.newNextLinks_stack.pop();
		// wipe new follows link
		_state.newFollowsLink = NULL;
		// set follows flag flase
		_state.setFollows = false;

		_state.currProcNode = new procNode( proc_id );
		this->_ast->addNewProcedure( _state.currProcNode );
		_state.numProcs++;

	};

	void addAssign( VAR_INDEX var_id ) {
		_state.numStmts++;
		_state.currStmtNode = new assignment_stmtNode( _state.numStmts, var_id, _state.currProcNode->getProcId() );
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		// set parent if any..
		if ( !(_state.bts.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.bts.top()->container;
		};
		_ast->addStmt (_state.currStmtNode);
		// initialize all next links if any..
		while(!(_state.newNextLinks_stack.empty())) {
			*(_state.newNextLinks_stack.top()) = _state.currStmtNode;
			_state.newNextLinks_stack.pop();
		}
		// initialise follows link
		if( _state.setFollows == true)
			*(_state.newFollowsLink) = _state.currStmtNode;

		// post next link of assign statement
		_state.newNextLinks_stack.push(&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
		// post follows link of assign statement
		_state.setFollows = true;
		_state.newFollowsLink = &(_state.currStmtNode->links[stmtNode::FOLLOWS]);
	};

	void setRhsSyntacticExprString ( STRING &ess ) {
		//_state.cusetSyntacticExprString( ess );
	((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setSyntacticExprString( ess );
	};

	void beginExpr ( OP_TYPE operation ) {
		OP_TYPE op = operation;
		composite_expr *e;
		switch(op) {
		case OP_Plus:
			e = new composite_expr( OP_Plus );
			break;
		case OP_Minus:
			e = new composite_expr( OP_Minus );
			break;
		case OP_Times:
			e = new composite_expr( OP_Times );
			break;
		default:
			cout << " invalid expression! " << endl;
			break;
		};
		_state.exprStack.push(e);
		// [debugging messages]
		//cout << "begin new expression.." << endl;
		// [/debugging messages]

	};

	void setLeftBranch ( NUMERIC_EXPR_TYPE net, INDEX id ) {
		if( net == NUMERIC_EXPR_TYPE_VARIABLE ) {
			((composite_expr*)_state.exprStack.top())->setLHS (new numeric_expr( id ));
		} else 
			((composite_expr*)_state.exprStack.top())->setLHS (new numeric_expr( id ));
	};

	void setRightBranch ( NUMERIC_EXPR_TYPE net, INDEX id ) {
		if( net == NUMERIC_EXPR_TYPE_VARIABLE ) {
			((composite_expr*)_state.exprStack.top())->setRHS (new numeric_expr( id ));
		} else 
			((composite_expr*)_state.exprStack.top())->setRHS (new numeric_expr( id ));
	};

	void addNumericExpr( NUMERIC_EXPR_TYPE net, INDEX id ) {
		NUMERIC_EXPR_TYPE et = net;
		switch (et) {
		case NUMERIC_EXPR_TYPE_VARIABLE:
			((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS( new numeric_expr( id ) );
			break;
		case NUMERIC_EXPR_TYPE_CONSTANT:
			((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS( new numeric_expr( id ) );
			break;
		default:
			break;
		};
	};

	expr* endExpr( BOOLEAN b) {
		if( _state.exprStack.empty() 
			|| ((composite_expr*)_state.exprStack.top())->getLHS() == NULL 
			|| ((composite_expr*)_state.exprStack.top())->getRHS() == NULL ) {
				cout << "invalid expression!" << endl;
				return NULL;
		} else {// [debugging messages]
			//cout << "subexpression complete: stack size: " << _state.exprStack.size() << endl;
			// [debugging messages]
			expr *top_of_stack_expr = _state.exprStack.top();
			_state.exprStack.pop();
			if( _state.exprStack.size() == 0) {
				//cout << "end of expression" << " stack size: " << _state.exprStack.size() << endl;
				((assignment_stmtNode*)_state.currProcNode->stmtList_Tail())->setRHS(top_of_stack_expr);
			}else {
				if ( b == composite_expr::ADD_TO_LHS ) {
					_state.exprStack.top()->setLHS(top_of_stack_expr);
				}else {
					_state.exprStack.top()->setRHS(top_of_stack_expr);
				}
			}
			return top_of_stack_expr;
		}
	};

	void addCall( PROC_INDEX proc_id ) {
		_state.numStmts++;
		_state.currStmtNode = new call_stmtNode(_state.numStmts, proc_id, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		// set parent if any..
		if ( !(_state.bts.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.bts.top()->container;
		};
		_ast->addStmt (_state.currStmtNode);
		// initialize all next links if any..
		while(!(_state.newNextLinks_stack.empty())) {
			*(_state.newNextLinks_stack.top()) = _state.currStmtNode;
			_state.newNextLinks_stack.pop();
		}
		// initialise follows link
		if( _state.setFollows)
			*(_state.newFollowsLink) = _state.currStmtNode;

		// post call next link
		_state.newNextLinks_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_STMT]));
		_state.setFollows = true;
		// post call follows link
		_state.newFollowsLink = &(_state.currStmtNode->links[stmtNode::FOLLOWS]);

	}

	void beginIf( VAR_INDEX condition_variable ) {
		//cout << "\n: beginIF" << endl;
		_state.numStmts++;
		_state.currStmtNode = new if_stmtNode( _state.numStmts, condition_variable, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		// set parent link if any..
		if ( !(_state.bts.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.bts.top()->container;
		};
		_ast->addStmt (_state.currStmtNode);
		// initialize all next links if any..
		while(!(_state.newNextLinks_stack.empty())) {
			*(_state.newNextLinks_stack.top()) = _state.currStmtNode;
			_state.newNextLinks_stack.pop();
		}
		// initialize follows link
		if( _state.setFollows)
			*(_state.newFollowsLink) = _state.currStmtNode;

		// start new block
		_state.bts.push(new block(_state.currStmtNode));

		// post next then link..
		_state.newNextLinks_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_TO_THEN]));
		// set follows flag false..
		_state.setFollows = false;
	};

	void endIf( if_stmtNode::IF_BLOCK_TYPE blk_type ) {
		//cout << "\n: endIf: "<< blk_type << endl;
		if( !(_state.bts.empty()) 
			&& _state.bts.top()->container->getStmtType() == STATEMENT_If) {
				switch (blk_type) {
				case if_stmtNode::THEN_BLOCK:// end THEN block
					if (_state.bts.top()->container->links[stmtNode::NEXT_TO_THEN] != NULL ) {

						// wipe folows
						_state.setFollows = false;
						_state.newFollowsLink = NULL;
						// save next links of then block in parent
						while( !(_state.newNextLinks_stack.empty()) ) {
							_state.bts.top()->blockNext_tails.push( _state.newNextLinks_stack.top() );
							_state.newNextLinks_stack.pop();
						}

						// post next else link...
						_state.newNextLinks_stack.push (&(_state.bts.top()->container->links[stmtNode::NEXT_TO_ELSE]));
					}
					break;

				case if_stmtNode::ELSE_BLOCK: // end ELSE block
					if (_state.bts.top()->container->links[stmtNode::NEXT_TO_ELSE] != NULL ) {
						// wipe follows..
						_state.setFollows = false;
						_state.newFollowsLink = NULL;

						// new Next links = else Links + then links
						while( !(_state.bts.top()->blockNext_tails.empty()) ) {
							_state.newNextLinks_stack.push( _state.bts.top()->blockNext_tails.top() );
							_state.bts.top()->blockNext_tails.pop();
						}
						// post parent follows link..
						_state.setFollows = true;
						_state.newFollowsLink = &(_state.bts.top()->container->links[stmtNode::FOLLOWS]);

						// end and remove block..
						_state.bts.pop();
					}
					break;

				default:
					cout << ": invalid end of If statement block" << endl;
					break;
				}
				//_state.currStmtNode = _state.container_stack.top()->_nextNode;
		}else {
			cout << "Invalid If block end" << endl;
		}
	};

	void beginWhile( VAR_INDEX condition_variable ) {
		//cout << "\n: beginWhlie" << endl;
		_state.numStmts++;
		_state.currStmtNode = new while_stmtNode( _state.numStmts, condition_variable, _state.currProcNode->getProcId());
		_state.currStmtNode->_proc_id = _state.currProcNode->getProcId();
		_state.currProcNode->setStmtListTail (_state.currStmtNode);
		// set parent if any..
		if ( !(_state.bts.empty())) {
			_state.currStmtNode->links[stmtNode::PARENT] = _state.bts.top()->container;
		};
		_ast->addStmt (_state.currStmtNode);
		// inilialize all next links if any..
		while(!(_state.newNextLinks_stack.empty())) {
			*(_state.newNextLinks_stack.top()) = _state.currStmtNode;
			_state.newNextLinks_stack.pop();
		}
		// initialize follows link
		if( _state.setFollows)
			*(_state.newFollowsLink) = _state.currStmtNode;

		//--------------------------------------------
		// start new block..
		_state.bts.push(new block(_state.currStmtNode));
		// post next to while link..
		_state.newNextLinks_stack.push (&(_state.currStmtNode->links[stmtNode::NEXT_TO_WHILE]));
		// set follows flag false..
		_state.setFollows = false;
	};

	void endWhile() {
		if( !(_state.bts.empty()) 
			&& _state.bts.top()->container->getStmtType() == STATEMENT_While) {
				if( _state.bts.top()->container->links[stmtNode::NEXT_TO_WHILE] != NULL ) {
					// set while loop.
					// all next links point to parent while statement.5
					while (!(_state.newNextLinks_stack.empty())) {
						*(_state.newNextLinks_stack.top()) = _state.bts.top()->container;
						_state.newNextLinks_stack.pop();
					}

					// post parent while follows link..
					_state.newNextLinks_stack.push (&(_state.bts.top()->container->links[stmtNode::NEXT_STMT]));
					// set follows flag flase..
					_state.newFollowsLink = &(_state.bts.top()->container->links[stmtNode::FOLLOWS]);
					_state.setFollows = true;

				}else {
					// cout << "\n\nInvalid while block";
				}
				//_state.currStmtNode = _state.container_stack.top()->_nextNode;
				_state.bts.pop();
		}else {
			cout << "Invalid While block end" << endl;
		}
	};

	// additional functions
	void listAllStmts();
};

#endif